package com.ztom.beans.factory.support.reader.xml

import cn.hutool.core.util.XmlUtil
import com.ztom.beans.exception.BeansException
import com.ztom.beans.factory.support.pojo.BeanDefinition
import com.ztom.beans.factory.support.pojo.BeanReference
import com.ztom.beans.factory.support.pojo.PropertyValue
import com.ztom.beans.factory.support.reader.AbstractBeanDefinitionReader
import com.ztom.beans.factory.support.registry.BeanDefinitionRegistry
import com.ztom.core.io.Resource
import com.ztom.core.io.ResourceLoader
import org.w3c.dom.Element
import java.io.IOException
import java.io.InputStream
import java.util.*
import kotlin.reflect.KClass

/**
 * @author ZhangTao
 */
class XmlBeanDefinitionReader : AbstractBeanDefinitionReader {

    companion object {
        const val BEAN_ATTRIBUTE = "bean"
        const val PROPERTY_ELEMENT = "property"
        const val ID_ATTRIBUTE = "id"
        const val CLASS_ATTRIBUTE = "class"
        const val NAME_ATTRIBUTE = "name"
        const val VALUE_ATTRIBUTE = "value"
        const val REF_ATTRIBUTE = "ref"
        const val INIT_METHOD_ATTRIBUTE = "init-method"
        const val DESTROY_METHOD_ATTRIBUTE = "destroy-method"
        const val SCOPE_ATTRIBUTE = "scope"
    }

    constructor(registry: BeanDefinitionRegistry) : super(registry)

    constructor(registry: BeanDefinitionRegistry, resourceLoader: ResourceLoader) : super(registry, resourceLoader)

    override fun loadBeanDefinitions(resource: Resource) {
        val inputStream = resource.getInputStream()
        try {
            doLoadBeanDefinitions(inputStream)
        } catch (e: IOException) {
            throw BeansException("IOException parsing XML document from $resource", e)
        } finally {
            inputStream.close()
        }
    }

    override fun loadBeanDefinitions(location: String) {
        loadBeanDefinitions(getResourceLoader().getResource(location))
    }

    private fun doLoadBeanDefinitions(inputStream: InputStream) {
        val document = XmlUtil.readXML(inputStream)
        val root = document.documentElement
        val childNodes = root.childNodes
        for (i in 0 until childNodes.length) {
            val beanNode = childNodes.item(i)
            if (beanNode !is Element) {
                continue
            }
            // 解析 bean 标签
            if (BEAN_ATTRIBUTE != beanNode.nodeName) {
                continue
            }
            val id = beanNode.getAttribute(ID_ATTRIBUTE)
            val className = beanNode.getAttribute(CLASS_ATTRIBUTE)
            val name: String by lazy { beanNode.getAttribute(NAME_ATTRIBUTE) }

            val clazz: KClass<out Any>
            try {
                clazz = Class.forName(className).kotlin
            } catch (e: ClassNotFoundException) {
                throw BeansException("Cannot find class [$className]", e)
            }

            // 优先取 id
            var beanName = id.ifBlank { name }
            // id 和 name 都为空, 默认处理为类名首字母小写
            beanName = beanName.ifBlank {
                clazz.simpleName!!.replaceFirstChar { it.lowercase(Locale.getDefault()) }
            }

            // 检查重复 beanName
            if (getRegistry().containsBeanDefinition(beanName)) {
                throw BeansException("Duplicate bean name [$beanName] is not allowed")
            }

            val beanDefinition = BeanDefinition(clazz)
            // 自定义的 init 和 destroy 方法
            beanDefinition.initMethodName = beanNode.getAttribute(INIT_METHOD_ATTRIBUTE)
            beanDefinition.destroyMethodName = beanNode.getAttribute(DESTROY_METHOD_ATTRIBUTE)
            // scope
            val scope = beanNode.getAttribute(SCOPE_ATTRIBUTE)
            if (!scope.isNullOrBlank()) {
                beanDefinition.scope = scope
            }
            // 解析属性
            for (j in 0 until beanNode.childNodes.length) {
                val propNode = beanNode.childNodes.item(j)
                if (propNode !is Element) {
                    continue
                }
                if (PROPERTY_ELEMENT != propNode.nodeName) {
                    continue
                }
                val propName = propNode.getAttribute(NAME_ATTRIBUTE)
                if (propName.isNullOrBlank()) {
                    throw BeansException("The name attribute of property tag cannot be blank")
                }
                // property 的值 优先取 ref
                val ref = propNode.getAttribute(REF_ATTRIBUTE)
                val value: Any = if (ref.isNotBlank()) BeanReference(ref) else propNode.getAttribute(VALUE_ATTRIBUTE)

                beanDefinition.propertyValues.addPropertyValue(PropertyValue(propName, value))
            }
            // 注册 BeanDefinition
            getRegistry().registerBeanDefinition(beanName, beanDefinition)
        }
    }
}